/*
 * Background Flickr: Copyright (C) 2009  Craig McNicholas
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.ddw.app.bflickr.flickr.api.request;

import java.io.IOException;
import java.util.Vector;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import com.ddw.app.bflickr.flickr.api.FlickrPhoto;
import com.ddw.app.bflickr.flickr.api.FlickrResponse.FlickrResponseErrorCode;
import com.ddw.app.bflickr.flickr.api.response.FlickrPhotosResponse;
import com.ddw.app.bflickr.flickr.api.response.FlickrResponsePhoto;
import com.ddw.app.bflickr.flickr.api.url.FlickrPhotosURL;
import com.ddw.app.bflickr.flickr.api.utils.XmlUtils;
import com.ddw.app.bflickr.logging.Log;
import com.ddw.app.bflickr.logging.LogFactory;

/**
 * Class to request, parse and respond to a flickr get 
 * photos service call. The request is made asynchronously so 
 * listeners must be in place to catch the response.
 * @author craig
 *
 */
public class FlickrPhotosRequest extends FlickrApiRequest {

	/**
	 * Enumeration for the flickr response xml tag names.
	 * <br/><b>ERROR</b> The error tag if present in the xml document
	 * <br/><b>PHOTOS</b> The photos tag if present in the xml document, contains individual photo elements
	 * <br/><b>PHOTOS_PHOTO</b> The photo tag which is a child of PHOTOS if present in the xml document
	 * @author craig
	 *
	 */
	private enum FlickrResponseTagName {
		ERROR (),
		PHOTOS (),
		PHOTOS_PHOTO ();
		
		private Log log = LogFactory.createInstance(this.getClass());
		
		/**
		 * Getter of the tag name for the enumerated value
		 * @return the tag name
		 */
		public String getTag() {
			switch(this) {
			case ERROR: return "err";
			case PHOTOS: return "photos";
			case PHOTOS_PHOTO: return "photo";
			}
			log.error("Could not assert the value of the enumerated type " + this);
			throw new AssertionError("Could not assert the value of the enumerated type " + this);
		}
	}
	
	/**
	 * Enumeration for the flickr response xml attribute names.
	 * <br/><b>ERROR_CODE</b> The error code attribute of the ERROR tag if present 
	 * in the xml document, <i>this is an integer</i>
	 * <br/><b>ERROR_MESSAGE</b> The message attribute of the ERROR tag if present 
	 * in the xml document
	 * <br/><b>PHOTOS_PAGE_NUMBER</b> The page number currently being viewed (pagination)
	 * <br/><b>PHOTOS_TOTAL_PAGES</b> The total number of pages (pagination)
	 * <br/><b>PHOTOS_TOTAL_IMAGES_PER_PAGE</b> The total number of images being shown per page
	 * <br/><b>PHOTOS_TOTAL_IMAGES</b> The total number of images across all pages
	 * <br/><b>PHOTOS_PHOTO_ID</b> The unique photos ID
	 * <br/><b>PHOTOS_PHOTO_OWNER</b> The ID of the owner of the photo (NSID)
	 * <br/><b>PHOTOS_PHOTO_SECRET</b> The secret generated by flickr
	 * <br/><b>PHOTOS_PHOTO_SERVER</b> The server the photo is held on
	 * <br/><b>PHOTOS_PHOTO_FARM</b> The static farm used to retrieve the photo
	 * <br/><b>PHOTOS_PHOTO_TITLE</b> The photos title set by the user
	 * <br/><b>PHOTOS_PHOTO_IS_PUBLIC</b> Flag indicating if the photo is public (1 = true, 0 = false)
	 * <br/><b>PHOTOS_PHOTO_IS_FRIEND</b> Flag indicating if the photo is from a friends account (1 = true, 0 = false)
	 * <br/><b>PHOTOS_PHOTO_IS_FAMILY</b> Flag indicating if the photo is from a family members account (1 = true, 0 = false)
	 * @author craig
	 *
	 */
	private enum FlickrResponseAttributeName {
		ERROR_CODE (),
		ERROR_MESSAGE (),
		PHOTOS_PAGE_NUMBER (),
		PHOTOS_TOTAL_PAGES (),
		PHOTOS_TOTAL_IMAGES_PER_PAGE (),
		PHOTOS_TOTAL_IMAGES (),
		PHOTOS_PHOTO_ID (),
		PHOTOS_PHOTO_OWNER (),
		PHOTOS_PHOTO_SECRET (),
		PHOTOS_PHOTO_SERVER (),
		PHOTOS_PHOTO_FARM (),
		PHOTOS_PHOTO_TITLE (),
		PHOTOS_PHOTO_IS_PUBLIC (),
		PHOTOS_PHOTO_IS_FRIEND (),
		PHOTOS_PHOTO_IS_FAMILY ();
		
		private Log log = LogFactory.createInstance(this.getClass());
		
		/**
		 * Getter of the attribute name for the enumerated value
		 * @return the attribute name
		 */
		public String getTag() {
			switch(this) {
			case ERROR_CODE: return "code";
			case ERROR_MESSAGE: return "msg";
			case PHOTOS_PAGE_NUMBER: return "page";
			case PHOTOS_TOTAL_PAGES: return "pages";
			case PHOTOS_TOTAL_IMAGES_PER_PAGE: return "perpage";
			case PHOTOS_TOTAL_IMAGES: return "total";
			case PHOTOS_PHOTO_ID: return "id";
			case PHOTOS_PHOTO_OWNER: return "owner";
			case PHOTOS_PHOTO_SECRET: return "secret";
			case PHOTOS_PHOTO_SERVER: return "server";
			case PHOTOS_PHOTO_FARM: return "farm";
			case PHOTOS_PHOTO_TITLE: return "title";
			case PHOTOS_PHOTO_IS_PUBLIC: return "ispublic";
			case PHOTOS_PHOTO_IS_FRIEND: return "isfriend";
			case PHOTOS_PHOTO_IS_FAMILY: return "isfamily";
			}
			log.error("Could not assert the value of the enumerated type " + this);
			throw new AssertionError("Could not assert the value of the enumerated type " + this);
		}
	}
	
	private Log log = LogFactory.createInstance(this.getClass());
	
	/**
	 * Creates a new find api request instance to request a response from a 
	 * REST url and provide callbacks to handle to returned data. The response 
	 * should return a list of public photos after querying the service.
	 * @param url the url to request a response from
	 * @throws IllegalArgumentException when the url is null, <i>url's cannot be null</i>
	 */
	public FlickrPhotosRequest(FlickrPhotosURL url) throws IllegalArgumentException {
		super(url);
	}

	@Override
	protected FlickrPhotosResponse parseResponse(String response) throws Throwable {
		log.debug("Beginning parse of response string");
		XmlPullParser parser = XmlUtils.createXmlParser(response);
		FlickrPhotosResponse res = new FlickrPhotosResponse();
		parseXml(parser, res);
		return res;
	}

	/**
	 * Method to loop through the xml pull parser line by line and attempt to
	 * read the response from a flickr photo service
	 * @param parser the pull parser instance
	 * @param res the response object to return to listening objects
	 * @throws XmlPullParserException when the pull parser encounters a problem reading the xml
	 * @throws IOException if the string buffer fails
	 */
	private void parseXml(XmlPullParser parser, FlickrPhotosResponse res) throws XmlPullParserException, IOException {
		int event = parser.getEventType();
		while(event != XmlPullParser.END_DOCUMENT) { // Loop through nodes catching certain elements
			if(event == XmlPullParser.START_TAG) {
				for(FlickrResponseTagName tag : FlickrResponseTagName.values()) {
					if(tag.getTag().compareToIgnoreCase(parser.getName()) == 0) {
						switch(tag) {
						case ERROR:
							parseXmlStartErrorTag(parser, res);
							break;
						case PHOTOS:
							parseXmlStartPhotosTag(parser, res);
							break;
						case PHOTOS_PHOTO:
							parseXmlStartPhotosPhotoTag(parser, res);
							break;
						}
					}
				}
			}
			event = parser.next();
		}
	}

	/**
	 * Method to parse each photo tag. This method attempts to retrieve the individual photo 
	 * details and append them to the vector of photos in the response object.
	 * @param parser the pull parser instance
	 * @param res the response object to return to listening objects 
	 */
	private void parseXmlStartPhotosPhotoTag(XmlPullParser parser, FlickrPhotosResponse res) {
		log.debug("Parser found PHOTOS_PHOTO start tag.");
		String photoId = parser.getAttributeValue("", FlickrResponseAttributeName.PHOTOS_PHOTO_ID.getTag());
		//String owner = parser.getAttributeValue("", FlickrResponseAttributeName.PHOTOS_PHOTO_OWNER.getTag());
		String secret = parser.getAttributeValue("", FlickrResponseAttributeName.PHOTOS_PHOTO_SECRET.getTag());
		String server = parser.getAttributeValue("", FlickrResponseAttributeName.PHOTOS_PHOTO_SERVER.getTag());
		String farm = parser.getAttributeValue("", FlickrResponseAttributeName.PHOTOS_PHOTO_FARM.getTag());
		//String title = parser.getAttributeValue("", FlickrResponseAttributeName.PHOTOS_PHOTO_TITLE.getTag());
		//String isPublic = parser.getAttributeValue("", FlickrResponseAttributeName.PHOTOS_PHOTO_IS_PUBLIC.getTag());
		//String isFriend = parser.getAttributeValue("", FlickrResponseAttributeName.PHOTOS_PHOTO_IS_FRIEND.getTag());
		//String isFamily = parser.getAttributeValue("", FlickrResponseAttributeName.PHOTOS_PHOTO_IS_FAMILY.getTag());
		FlickrResponsePhoto photo = new FlickrResponsePhoto(farm, server, photoId, secret);
		res.getPhotos().add(photo);
	}
	
	/**
	 * Method to parse the starting photos tag. This method attempts to retrieve the 
	 * number of the page, the total number of pages returned and the total number of 
	 * photos found.
	 * @param parser the pull parser instance
	 * @param res the response object to return to listening objects 
	 */
	private void parseXmlStartPhotosTag(XmlPullParser parser, FlickrPhotosResponse res) {
		log.debug("Parser found PHOTOS start tag.");
		if(res.getPhotos() == null) {
			log.debug("Photos vector was empty, creating new vector.");
			res.setPhotos(new Vector<FlickrPhoto>());
		}
		res.setResponseCode(FlickrResponseErrorCode.SUCCESS); // If there is a photos tag the request was successful
		String pageNumber = parser.getAttributeValue("", FlickrResponseAttributeName.PHOTOS_PAGE_NUMBER.getTag());
		String totalPages = parser.getAttributeValue("", FlickrResponseAttributeName.PHOTOS_TOTAL_PAGES.getTag());
		String totalImages = parser.getAttributeValue("", FlickrResponseAttributeName.PHOTOS_TOTAL_IMAGES.getTag());
		//String imagesPerPage = parser.getAttributeValue("", FlickrResponseAttributeName.PHOTOS_TOTAL_IMAGES_PER_PAGE.getTag());
		
		res.setPage(XmlUtils.parseInt(pageNumber, 1));
		res.setTotalPages(XmlUtils.parseInt(totalPages, 0));
		res.setTotalPhotos(XmlUtils.parseInt(totalImages, 0));
	}
	
	/**
	 * Method to parse the starting error tag. This method attempts to retrieve 
	 * the error code sent with the error tag and update the response code.
	 * @param parser the pull parser instance
	 * @param res the response object to return to listening objects 
	 */
	private void parseXmlStartErrorTag(XmlPullParser parser, FlickrPhotosResponse res) {
		log.debug("Parser found ERROR start tag.");
		String errorCode = parser.getAttributeValue("", FlickrResponseAttributeName.ERROR_CODE.getTag());
		FlickrResponseErrorCode code = FlickrResponseErrorCode.parseCode(errorCode);
		res.setResponseCode(code);
	}

}
